package socket

import (
	"github.com/gorilla/websocket"
	"net/http"
)

// WebSocketAdapter interface
type WebSocketAdapter interface {
	Connect() error
	Disconnect() error
	ReadMessage() ([]byte, error)
	WriteMessage([]byte) error
}

// GorillaWebSocketAdapter adapter for gorilla/websocket
type GorillaWebSocketAdapter struct {
	conn         *websocket.Conn
	ReadChannel  chan []byte
	WriteChannel chan []byte
}

// Connect connects to the server
func (adapter *GorillaWebSocketAdapter) Connect() error {
	return adapter.conn.WriteMessage(websocket.TextMessage, []byte("ping"))
}

// Disconnect disconnects from the server
func (adapter *GorillaWebSocketAdapter) Disconnect() error {
	return adapter.conn.Close()
}

// ReadMessage reads a message from the server
func (adapter *GorillaWebSocketAdapter) ReadMessage() ([]byte, error) {
	return <-adapter.ReadChannel, nil
}

// WriteMessage writes a message to the server
func (adapter *GorillaWebSocketAdapter) WriteMessage(message []byte) error {
	adapter.WriteChannel <- message
	return nil
}

// WebSocketFactory creates WebSocketAdapter instances
type WebSocketFactory struct {
	adapter WebSocketAdapter
}

// NewWebSocketFactory creates a new WebSocketFactory
func NewWebSocketFactory() *WebSocketFactory {
	return &WebSocketFactory{&GorillaWebSocketAdapter{}}
}

// CreateClient creates a new WebSocketAdapter for test
func (factory *WebSocketFactory) CreateClient(url string) WebSocketAdapter {
	adapter := &GorillaWebSocketAdapter{}
	conn, _, err := websocket.DefaultDialer.Dial(url, nil)
	if err != nil {
		panic(err)
	}
	adapter.conn = conn
	adapter.ReadChannel = make(chan []byte)
	adapter.WriteChannel = make(chan []byte)
	go adapter.startReading()
	go adapter.startWriting()
	return adapter
}

// CreateServer creates a new WebSocketAdapter for server
func (factory *WebSocketFactory) CreateServer(w http.ResponseWriter, r *http.Request) WebSocketAdapter {
	adapter := &GorillaWebSocketAdapter{}
	conn, err := websocket.Upgrade(w, r, nil, 1024, 1024)
	if err != nil {
		panic(err)
	}
	adapter.conn = conn
	adapter.ReadChannel = make(chan []byte)
	adapter.WriteChannel = make(chan []byte)
	go adapter.startReading()
	go adapter.startWriting()
	return adapter
}

// startReading reads messages from the server and writes them to the ReadChannel
func (adapter *GorillaWebSocketAdapter) startReading() {
	for {
		_, message, err := adapter.conn.ReadMessage()
		if err != nil {
			break
		}
		adapter.ReadChannel <- message
	}
	close(adapter.ReadChannel)
}

// startWriting reads messages from the WriteChannel and writes them to the server
func (adapter *GorillaWebSocketAdapter) startWriting() {
	for message := range adapter.WriteChannel {
		err := adapter.conn.WriteMessage(websocket.TextMessage, message)
		if err != nil {
			break
		}
	}
	close(adapter.WriteChannel)
}
